Projekt na zaliczenie przedmiotu Eksploracja danych
Author
Kacper Gałan
Published
June 8, 2024
Celem projektu jest wstępna analiza zbioru danych oraz stworzenie na jego podstawie modeli klasyfikacyjnych. Zestaw danych, na którym będę pracował, został upubliczniony blisko ponad trzydzieści lat temu dla serwisu internetowego UC Irvine Machine Learning Repository, służącemu do zamieszczania ciekawych z punktu widzenia wszelkich nauk o danych zbiorów. Podstrona, na której możemy pobrać zadaną ramkę danych (Mushroom. (1987). UCI Machine Learning Repository. https://doi.org/10.24432/C5959T.), tłumaczy z czego się ona składa: mamy opisy hipotetycznych próbek odpowiadających 23 gatunkom grzybów z rodziny Agaricus i Lepiota, gdzie każdy z gatunków jest identyfikowany jak jadalny (ang. edible), bądź trujący (ang. poisonous). Naszym celem będzie wyuczenie klasyfikatora, który na podstawie innch cech zawartych w danych, będzie przewidywał czy dany grzyb jest jadalny.
Plan pracy:
Wstępna analiza oraz wizualizacja zbioru
Przygotowanie danych dla algorytmów uczenia maszynowego - preprocessing
Uczenie klasyfikatorów, walidacja oraz porównanie osiągów na zbiorze testowym
Analiza błedów modeli oraz wnioski końcowe
Ad 1.
Zaczynamy od przedstawienia zbioru oraz podstawowych statystyk opisowych.
Pierwszych sześć obserwacji:
class
cap-shape
cap-surface
cap-color
bruises
odor
gill-attachment
gill-spacing
gill-size
gill-color
...
stalk-surface-below-ring
stalk-color-above-ring
stalk-color-below-ring
veil-type
veil-color
ring-number
ring-type
spore-print-color
population
habitat
0
p
x
s
n
t
p
f
c
n
k
...
s
w
w
p
w
o
p
k
s
u
1
e
x
s
y
t
a
f
c
b
k
...
s
w
w
p
w
o
p
n
n
g
2
e
b
s
w
t
l
f
c
b
n
...
s
w
w
p
w
o
p
n
n
m
3
p
x
y
w
t
p
f
c
n
n
...
s
w
w
p
w
o
p
k
s
u
4
e
x
s
g
f
n
f
w
b
k
...
s
w
w
p
w
o
e
n
a
g
5 rows × 23 columns
Aby zrozumieć co oznaczają poszczególne wartości w wierszach należy posłużyć się zamieszczoną przez autorów legendą:
Zatem poza cechą class, według której będziemy klasyfikowali obserwacje, mamy do dyspozycji pozostałe 22 cechy, które opisują parametry skatalogowanych obserwacji grzybów. Niektóre z nich są wizualnie przedstawione na stronie DataSciencePlus w podrozdziale Mushroom Features by pictures.
Zauważamy, że potencjalnie nie występują braki danych, natomiast w notatce od autorów wiemy, że takowe występują w kolumnie stalk-root i są oznaczone przez symbol znaku zapytania. Dalej będziemy musieli się tym zająć.
Poniższa tabela przedstawia odpowiednio:
count - liczba obserwacji bez braków danych danej cechy
unique - liczba unikalnych wartości w kolumnie
top - najczęściej pojawiającą się wartość danej kolumny
freq - częstotliwość występowania najczęściej pojawiającej się wartości danej kolumny
data.describe()
class
cap-shape
cap-surface
cap-color
bruises
odor
gill-attachment
gill-spacing
gill-size
gill-color
...
stalk-surface-below-ring
stalk-color-above-ring
stalk-color-below-ring
veil-type
veil-color
ring-number
ring-type
spore-print-color
population
habitat
count
8124
8124
8124
8124
8124
8124
8124
8124
8124
8124
...
8124
8124
8124
8124
8124
8124
8124
8124
8124
8124
unique
2
6
4
10
2
9
2
2
2
12
...
4
9
9
1
4
3
5
9
6
7
top
e
x
y
n
f
n
f
c
b
b
...
s
w
w
p
w
o
p
w
v
d
freq
4208
3656
3244
2284
4748
3528
7914
6812
5612
1728
...
4936
4464
4384
8124
7924
7488
3968
2388
4040
3148
4 rows × 23 columns
Przejdźmy do etapu wizualizacji danych w celu obserwacji jakimi cechami wyróżniają się grzyby trujące. Rozpoczynamy od liczebności danych klas.
Zauważamy, że klasy w tym zbiorze danych są odpowiednio zbilansowane, dzięki czemu nie powinny wystąpić problemy ze sztucznie wysoki współczynnikiem accuracy wynikającym z nierównomiernego rozkładu liczebności klas docelowych.
Zatem z powyższego wykresu możemy wnioskować, że słabym wyznacznikiem tego, czy grzyb jest trujący są kolory oznaczone przeza symbol g (szary) oraz n (brązowy). Obserwując rozkład wartości dla innych najczęściej występujących kolorów, przykładowo dla koloru w (biały), widzimy, że grzyby jadalne występują ponad dwa razy częściej niż ich trujące odpowiedniki o tym ubarwienu, nieco odwrotnie ma się sytuacja jeżeli przyjrzymy się żółtym słupkom wykresu - niespełna 60% więcej grzybów o kapeluszu tego koloru to grzyby trujące. Kolor czerwony, którym kojarzy się z grzybami trującymi jest nie do końca dobrym wyróżnikiem tego, czy dany grzyb faktycznie jest niejadalny, ponieważ niespełna 30% więcej grzybów o tym zabarwieniu okazuje się być trujące. Natomiast w przypadku kolorów r (zielony) oraz u (fioletowy) możemy mieć całkowitą pewność, że natrafiliśmy na grzyby jadalne.
Zajmiemy się teraz wizualizacją, jak wartość cechy odor (zapach) wpływa na zmienną class.
<Figure size 1000x500 with 0 Axes>
Zapach grzyba ma ogromny wpływ na jego klasę. W przypadku zapachów oznaczonych symbolami: a (migdał), l (anyż) oraz n (brak zapachu) możemy mieć pewność (z małym marginesem błędu w przypadku braku występowania zapachu), że dany grzyb będzie jadalny. W każdym z pozostałych wartości zmiennej odor mamy do czynienia z grzybami niejadalnymi. Cecha ta świetnie dywersyfikuje klasę grzybów.
<Figure size 1000x500 with 0 Axes>
W przypadku zmiennej gill-size nie dostrzegamy już tak ogromnego rozróżnienia ze względu na tę cechę, aczkolwiek nadal jest ono znaczące. Ponad dwa razy więcej grzybów o omawianej cesze równej wartości b (rozległy) to grzyby jadalne, podczas gdy niespełna osiem razy więcej jest grzybów trujących w przypadku pozostałej wartości cechy.
<Figure size 1000x500 with 0 Axes>
Powyższy wykres obrazuje rozkład zmiennej habitat (miejsce występowania) w podziale na klasę grzybów. Niemalże dwa razy więcej grzybów występująych na tereniu typu g (trawy) jest jadalnych. Grzyby trujące stanowią większość w miejscach występowania typu l (obszary liściaste), p (przy ścieżkach) oraz u (tereny miejskie). Grzyby niejadalne nie występują na terenach typu w (odpady). Tabela poniżej pomaga w bardziej wnikliwym wglądzie w jaki sposób teren występowania grzybów różnicuje to czy są one jadalne. Wyniki wyrażają jaki procent całej obserwacji danej cechy stanowią zadane klasy.
class
e
p
habitat
d
59.72
40.28
g
65.55
34.45
l
28.85
71.15
m
87.67
12.33
p
11.89
88.11
u
26.09
73.91
w
100.00
0.00
Ostatnią cechą, której się dogłębniej przyjrzymy będzie zmienna population, wyjaśniająca w jakiego rodzaju skupiskach występują zebrane obserwacje grzybów.
<Figure size 1000x500 with 0 Axes>
Dla pierwszych trzech rodzajów skupisk jesteśmy praktycznie pewny, że zebrane grzyby są jadalne, gdyż stanowią one około:
'99.95%'
wszystkich grzybów występujących w tego rodzaju populacjach. Jeśli chodzi o aglomeracje typu s (rozsiane) oraz y (samotnie) to wciąż grzyby jadalne występują częściej niż ich trujące odpowiedniki, choć ich wkład w liczebność wzrósł. Grzyby występujące po kilka (zmienna v) to w zdecydowanej mierze grzyby niejadalne.
Ad 2.
Pierwszą sprawą, którą się zajmiemy w etapie preprocessingu będzie analiza oraz ewentualna imputacja brakujących wartości cechy stalk-root. Rozkład wartości tej cechy ma się następująco:
stalk-root
b 3776
? 2480
e 1120
c 556
r 192
Name: count, dtype: int64
Zauważamy zatem, że brakujące informacje stanowią około:
'30.53%'
wszystkich wartości tej cechy w omawionym zbiorze. Jest to bardzo duży odsetek braków informacji.
Przyjrzyjmy się dodatkowo jak cecha stalk-root ma się do tego czy dany grzyb jest jadalny.
<Figure size 1000x500 with 0 Axes>
Takie wartości tej zmiennej jak: c, e oraz r są zdominowane przez grzyby uznawane za jadalne. Największą konkurencję względem grzybów jadalnych obserwujemy, gdy wartość cechy to b.
Zdecyduję się na pominięcie tej zmiennej przy dalszej analizie oraz budowie modelu. Przy tak dużym procencie braków oraz ich charakterystyce (braki występują falami, gdzie w kilkuset ostatnich wierszach zbioru danych występują jedynie informacje oznaczone jako brakujące). W takim przypadku, przykładowo chcąc posłużyć się metodą imputacji najbliższych sąsiadów (funkcja KNNImputer) finale wyniki mogą być zaburzone.
data = data.drop(['stalk-root'], axis =1)
Kolejnym krokiem jest konwersja zmiennych kategorycznych na zmienne liczbowe. Dla zmiennej class (zmienna target) posłużymy się funkcją LabelEncoder, która dla każdej zmiennej w danej kolumnie przypisze wartość od 0 do n-1, gdzie n to liczba unkialnych wartości danej kolumny. Wartości przypisywane są zgodnie z porządkiem alfabetycznym pierwotnych zmiennych. Ze względu na to, że pozostałe cechy nie są względem siebie w żaden sposób zhierarchizowane (nie możemy ich uporządkować), należy wobec nich użyć funkcji OneHotEncoder, która podzieli daną kolumnę z cechą na tyle innych kolumn, ile jest unikatowych wartości danej cechy, a następnie będzie przypisywała wartość 1 dla wystąpień danej wartrości, a pozostałym 0. Tak dla każdej wartości danej cechy. W ten sposób unikniemy możliwych błędnych interpretacji współzależności zmiennych przez algorytmy ML.
Zauważmy, że nasz zbiór cech po transformacji ma aż:
112
nowych cech. Taka liczba zmiennych może stanowić spory problem przy tworzeniu modeli - może doporowadzić do przeuczenia się danych treningowych przez algorytmy klasyfikacyjne. W celu zapobiegnięcia temu posłużymy się jedną z technik redukcji wymiarowości - PCA.
from sklearn.decomposition import PCApca = PCA()pca.fit_transform(X)
Wizualizacja wkładu poszczególnych składowych w wyjaśnianie wariancji pierwotnego zestawu danych:
Obserwujemy, że w okolicach dziesiątego komponentu wkład w wyjaśnioną wariancję mało się zmienia. Zatem założymy, że PCA z dziesięcioma komponentami będzie najbardziej optymalnym rozwiązaniem - między redukcją wymiarów/zmiennych a utratą wyjaśnionej wariancji.
Wymiary nowego zbioru cech:
(8124, 10)
Tworzymy zbiór uczący oraz testowy.
from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split( X_pca, y, test_size=0.2,random_state=42)
Kolejnie ręcznie przypisujemy etykiety dla zmiennej target. Jako sukces oznaczamy grzyby, które są jadalne (przypisana zostanie im wartość True).
y_test = (y_test =="e")y_train = (y_train =="e")
Ad 3. Przechodzimy do kolejnego etapu projektu. Zbudujemy kilka konkurencyjnych modeli w oparciu o przedstawiony powyżej zbiór testowy, następnie porównamy ich osiągi posługując się kroswalidacją.
Pracę z modelami zaczniemy od zbudowania najprostszego możliwego modelu klasyfikującego - model ten jest instancją funkcji DummyClassifier, który każdą obserwację przyporządkowuje do najczęściej występującej klasy. W naszym przypadku będzie to klasa odpowiadająca za grzyby jadalne.
from sklearn.model_selection import cross_val_scorefrom sklearn.dummy import DummyClassifierdummy_clf = DummyClassifier()dummy_clf.fit(X_train, y_train)
DummyClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
DummyClassifier()
Jakość doposowania modelu:
dummy_clf.score(X_train, y_train)
0.5177719649176796
Tak jak się spodziewaliśmy, accuracy tego modelu wynosi tyle ile stanowi odsetek najczęściej występującej etykiety. Pozostałe modele będą miały na celu przebić dokładność tego klasyfikatora.
Kolejnym modelem, który zbudujemy będzie model regresji logistycznej.
Implementacja za pomocą funkcji LogisticRegression:
from sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import accuracy_scorelogreg_clf = LogisticRegression()
Doposowanie modelu do zmiennych treningowych:
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
Accuracy wynosi:
0.9613786736420988
Sprawdźmy jak wygląda dokładność po wykorzystaniu techniki kroswalidacji
0.9604551430094155
Bardzo mała różnca (na korzyść zbioru testowego) w wartościach metryki może świadczyć o niewielkim przeuczeniu modelu. Przyjrzymy się jak wygląda krzywa uczenia.
Wykres powyżej potwierdza nasze założenie o niewielkim overfitting modelu. Poprzez optymalizację hiperparametrów postaramy się temu zapobiec. Definiujemy siatkę parametrów do tuningu, gdzie:
C: Odwrotność siły regulyrizacji. Mała wartość oznacza silną regulyrizację, duża wartość oznacza słabą regularizację.
tol: Tolerancja dla kryterium stopu w procesie optymalizacji. Proces zatrzyma się, gdy zmiana w funkcji kosztu między iteracjami będzie mniejsza niż tol. Mniejsza wartość oznacza bardziej precyzyjne kryterium zatrzymania.
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Notujemy znacze usprawnienie modeli. Między accuracy na zbiorze treningowym a wynikiem kroswalidacji nie ma już właścieiwe różnicy. Wykres poniżej pokazuje, że w zoptymalizowanym modelu nie dochodzi do overfittingu.
Wartości accuracy modelu walidacyjnego oraz testowego osiągaja plateau, oznacza to, że model nie ma tendencji do nadmiernego dopasowania do danych treningowych.
Model drzewa decyzyjnego.
Budowę tego modelu rozpoczynamy od stworzenia instancji funkcji DecisionTreeClassifier. Następnie tworzymy siątkę parametrów: max_depth (maksymalna liczba poziomów w drzewie) oraz max_features (maksymalna liczba cech brana pod uwagę przy tworzeniu nowego węzła), którą użuwamy w funkcji GridSearchCV. Funkcja ta polega na tworzeniu kombinacji parametrów, których wartości podajemy w siatce parametrów, minimializując zadaną metrykę (w naszym przypadku accuracy).
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
DecisionTreeClassifier()
1.0
Tak wysokie accuracy na zbiorze treningowym może być alarmujące ze względu na możliwe nadmierne dopasowanie do danych treningowych. Przeprowadzamy krosswalidację.
0.996615266181086
Również możemy wnioskować, że występuję nadmierne dopasowanie do zestawu treningowego. Przeprowadzimy proces optymalizacji hiperparametrów.
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Ceną niewielkiego spadku dokładnościu modelu otrzymaliśmy bardziej zoptymalizowany, lepiej uogólniający model.
Aby poprawnie ocenić jakość modelu, przenalizujemy krzywą uczenia, gdzie porównujemy przyrost/spadek accuracy z podziałem na zbiór testowy oraz walidacyjny, uzyskany poprzez przeprowadzenie kroswalidacji zbioru testowego.
Krzywa powyżej nadal świadczy o lekkim przetrenowaniu algorytmu. Porównajmy ją z wykresem przed tuningiem.
Wykres po optymalizacji wygląda lepiej, wartości dla zbioru testowego w pewnych etapach są mniejsze.
Model lasu losowego - rodzaj modelu zespołowego (ang. ensemble model), który wykorzystuje wiele pojedynczych, niepowiązanych ze sobą drzew decyzyjnych do przeprowadzenia klasyfikacji. Każde drzewo w lesie losowym jest trenowane na innym podzbiorze danych (próbce bootstrapowej), a następnie z rezultatów klasyfikacji z każdego drzewa wybierana jest najczęściej występująca klasa. Podczas wzrostu drzewa w modelu lasu losowego w każdym węźle bierzemy pod uwagę jedynie losowy podzbiór cech dostępnych w danym zbiorze, następnie z tego wylosowanego podzbioru (najczęściej wielkość tego podzbioru to \(\sqrt{n},\) gdzie \(n\) to liczba wszystkich dostępnych cech) wybieramy cechę, która najlepiej dzieli na węzły potomne.
Implementacja modelu.
from sklearn.ensemble import RandomForestClassifierrandom_clf = RandomForestClassifier(random_state=42)
Dopasowanie modelu do zbioru treningowego.
random_clf.fit(X_train, y_train)
RandomForestClassifier(random_state=42)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestClassifier(random_state=42)
Wartość accuracy tego modelu:
1.0
Zbiór walidacyjny:
0.9996923076923077
Krzywa uczenia modelu bez optymalizacji:
Ze względu na małe różnice w dokładności miedzy zbiorami trreningowym i walidacyjnym decydujemy się na pozostawienie modelu lasu losowego z jego podstawowymi parametrami.
Model SGDClassifier (Stochastic Gradient Descent Classifier) - przykład linowego klasyfikatora, który jest optymalizowany poprzez metodę stochastycznego spadku wzdłuż gradientu (SGD). Najpierw należy wyjaśnić jak przebiega ten algorytm optymalizacji. Ogólna koncepcja polega na iteratywnym poprawianiu parametrów \(\theta\) modelu w celu zminimalizaowania funkcji kosztu \(J\). Zaczynamy od przypisania losowych wartości parametom \(\theta\) (inicjalizacja losowa), następnie poprzez parametr learning rate (współczynnik uczenia) ustalamy jak bardzo w każdym kroku mają się zmeniać nowe wagi parametrów \(\theta\). Wartości \(\theta\) są uaktualniane do momentu przekroczenia zadanej liczby epok (parametr max_iter), bądź do momentu uzyskania optymalnej wartości funkcji straty. W przypadku stochastycznej wersji tego algorytmu z każdą epoką losujemy ze zbioru treningowego nową próbkę, którą użyjemy do obliczenia gradientu. Oznacza to, że zamiast obliczać gradient na podstawie całego zbioru danych, obliczamy go na podstawie pojedynczych próbek, co znacznie usprawni proces minimalizacji funkcji kosztu. W nazym modelu użyjemy parametru loss = "hinge", co oznacza, że algorytm SGD zostanie zaimplementowany do optymalizacji liniowej wersji modelu SVM. Dodatkowo ustawimy parametr early_stopping=True, co onacza implementacje algorytmu wczesnego zatrzymania - polega ona na monitorowaniu wydajności modelu na zestawie walidacyjnym podczas treningu i zatrzymaniu procesu optymalizacji, gdy wydajność zaczyna się pogarszać.
from sklearn.linear_model import SGDClassifiersgd_clf = SGDClassifier(random_state=42, early_stopping=True, penalty=None) # penalty = None -> brak regularyzacji
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Przebieg uczenia jest chaotyczny (krzywe uczenia nie są gładkie, wartości accuracy skaczą), co jest zrozumiałe, gdyż algorytm SGD jest bardzo wrażliwy na odpowiednie dopasowanie hiperparametrów dla zadanego problemu. Implementujemy siatkę parametrów, gdzie:
max_iter: Maksymalna liczba epok (iteracji), przez które algorytm będzie się uczył. Większa liczba epok może pozwolić na dokładniejsze dopasowanie modelu do danych treningowych, ale może również prowadzić do nadmiernego dopasowania.
eta0: Początkowa wartość współczynnika uczenia (learning rate). Współczynnik uczenia kontroluje, jak duże kroki są podejmowane podczas aktualizacji parametrów modelu. Mały współczynnik uczenia prowadzi do mniejszych kroków, co może zapewnić stabilniejsze, ale wolniejsze zbieżności. Z kolei duży współczynnik może przyspieszyć uczenie, ale z ryzykiem przeskakiwania minimum funkcji kosztu.
tol: Tolerancja dla kryterium zatrzymania. Jeśli zmiana funkcji kosztu między kolejnymi epokami jest mniejsza niż ta wartość, proces uczenia zostanie przerwany przed osiągnięciem maksymalnej liczby iteracji. Pozwala to na oszczędność czasu obliczeniowego, jeśli model już zbiega do minimum.
n_iter_no_change: Liczba epok, przez które funkcja kosztu musi się poprawiać, aby kontynuować uczenie. Jeśli funkcja kosztu nie poprawia się przez określoną liczbę epok, proces uczenia zostanie przerwany. Zapobiega to marnowaniu zasobów na dalsze uczenie, gdy model już osiągnął optymalne parametry.
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Po strojeniu hiperparametrów obie wartoście są zauważalnie wyższe.
Krzywa uczenie również wygląda lepiej - nie występują już nagłe skoki w wartości metryki accuracy, które mogły wynikać ze złego dobrania parametru eta0.
Model KNNClassifier (K-Nearest Neighbors Classifier) - algorytm k-najbliższych sąsiadów zakłada, że podobne do siebie obserwacje znajdują się w bliskim otoczeniu w danej przestrzeni. W naszym przypadku odległość między punktami będziemy liczyli Euklidesowo, gdzie \[d(A, B) = \sqrt{\sum_{i=1}^n (x_i - y_i)^2},\] wylicza odległość Euklidesową w \(n\) wymiarowej przestrzeni. Klasyfikacja jest przeprowadzona poprzez głosowanie większościowe - dla nowego punktu obliczamy jego odległość od wszystkich pozostałych obserwacji, wybieramy \(k\) najbliższych punktów, sprawdzamy etykiety tych punktów i finalnie poprzez metodę głosowania większościowego przypisujemy etykietę nowej obserwacji.
Implementacja oraz dopasowanie modelu.
from sklearn.neighbors import KNeighborsClassifierknn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_train)
KNeighborsClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Wartości accuracy wraz ze wzrostem próbki uczącej zbiegają się dla obu zbiorów. Zobaczmy, czy po tuningu parametru odpowiadającego za liczbę sąsiednich punktów osiągniemy perfekcyjną dokładność.
Definiujemy siatkę parametrów. Składać się ona będzie jedynie z parametru określającego liczbę najbliższych sąsiadów, których etykiety będziemy porównywać w celu przypisania etykiety nieznanej obserwacji.
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Zauważamy zatem, że każdy z modeli wypada bardzo dobrze. Jednakże klasyfikatory liniowe (regresja logistyczna, liniowe SVM z optymalizacją SVM) wypadają istotnie nieco gorzej od reszty modeli. Drzewo decyzyjna, czego mogliśmy się spodziewać, ma nieco gorszy performance od lasu losowego. Podsumowując, pod względem accuracy najlepsze są modele lasu losowego oraz k-najbliższych sąsiadów.
Ad 4. Analiza błedów modeli.
Przedstawimy zagadnienia, które będą pomocne przy analize błędów klasyfikacji modeli:
Confusion Matrix - macierz przedstawiająca liczbę prawdziwych pozytywnych (TP), prawdziwych negatywnych (TN), fałszywych pozytywnych (FP) i fałszywych negatywnych (FN) predykcji. Pozwala na bardziej szczegółową analizę błędów klasyfikacji oraz wyliczenie cennych wskaźników jakości klasyfikacji. Interpretacja wartości macierzy pomyłek w kontekście naszego problemu:
True Positive (TP): Liczba grzybów jadalnych, które zostały poprawnie sklasyfikowane jako jadalne.
True Negative (TN): Liczba grzybów trujących, które zostały poprawnie sklasyfikowane jako trujące.
False Positive (FP): Liczba grzybów trujących, które zostały błędnie sklasyfikowane jako jadalne.
False Negative (FN): Liczba grzybów jadalnych, które zostały błędnie sklasyfikowane jako trujące.
W kontekście naszego zadania kluczowym czynnikiem decydującym o poprawności klasyfikatora będzie liczba fałszywych pozytywów (FP), gdyż konsekwencje klasyfikacji grzyba trującdego jako jadalnego mogą nieść dla potencjalnego konsumenta tragiczne skutki. W obliczu tego będziemy przykładali większą wagę do tej miary niż do liczby fałszywych negatywów (FN). W kontekście metryk precision i recall będziemy bardziej się skupiali na tej pierwszej z nich, ponieważ im większa jest jej wartość, tym mniejsza jest szansa na klasyfikację grzyba trującego jako jadalnego.
Accuracy - wskaźnik, który mierzy, jak często model uczenia maszynowego poprawnie przewiduje wynik. Dokładność można obliczyć, dzieląc liczbę poprawnych przewidywań przez całkowitą liczbę przewidywań. \[Accuracy = \frac{Poprawne~predykcje}{Wszystkie~predykcje} = \frac{TP + TN}{TP + TN + FP + FN}\] Precision - odsetek prawidłowych pozytywnych predykcji wśród wszystkich próbek zaklasyfikowanych jako pozytywne. Wskaźnik ten odpowiada na pytanie jak często pozytywne przewidywania są prawidłowe. \[Precision = \frac{TP}{TP + FP}\] Recall - mierzy, jak często model uczenia maszynowego poprawnie identyfikuje pozytywne instancje (prawdziwie pozytywne, TP) ze wszystkich rzeczywistych pozytywnych próbek w zbiorze danych. \[Recall = \frac{TP}{TP+FN}\] F1 score - średnia harmoniczna metryk Recall oraz Precision. Zastosowanie średniej harmonicznej sprawia, że większe wagi są nadawane mniejszym wartościom, przez co wartość tej miary będzie wysoka jedynie,gdy wysokie są wartości miar precyzji oraz pełności. \[F1 = \frac{2 \cdot \text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}\] Krzywa ROCi> i AUC (Area Under Curve) - krzywa ROC to wykres przedstawiający zależność między precision a (1 - sensivity), gdzie \[sensivity = \frac{FP}{FP + TN},\] odpowiada za odsetek fałszywie zaklasyfikowanych negatywnych przypadków. Krzywa ROC pozwala zobaczyć, jak zmienia się wydajność modelu przy różnych progach decyzyjnych. Idealny model miałby krzywą, która przechodzi przez punkt (0,1) na wykresie, co oznaczałoby, że wszystkie pozytywne przypadki są prawidłowo klasyfikowane, a żadne negatywne przypadki nie są błędnie zaklasyfikowane jako pozytywne.
Natomiast AUC to pole pod krzywą ROC, które interpretujemy jako umiejętność modelu do rozróżniania klas. Im wyższa wartość AUC, tym lepsza wydajność modelu w rozróżnianiu klas pozytywnych i negatywnych. Interpretacja wartości AUC:
AUC = 0.5: Model losowy, który nie ma zdolności do rozróżniania między klasami. Taka wartość AUC oznacza, że model działa równie dobrze jak losowe zgadywanie.
0.5 < AUC < 1.0: Istnieje duże prawdopodobieństwo, że klasyfikator będzie w stanie odróżnić pozytywne wartości klasy od negatywnych. Dzieje się tak, ponieważ klasyfikator jest w stanie wykryć większą liczbę prawdziwych pozytywów i prawdziwych negatywów niż fałszywych negatywów i fałszywych pozytywów.
AUC = 1.0: Klasyfikator może poprawnie rozróżnić wszystkie obserwacje klasy pozytywnej i negatywnej.
Po wyjaśnieniu jakimi metrykami się posłużymi, przeanalizujmy nasze modele pod ich kątem. Przedstawione wskażniki wyliczamy już na podstawie zestawu testowego.
Model
Precision
Recall
AUC
F1
0
Regresja Logistyczna
0.953216
0.966785
0.992092
0.959953
1
Drzewo Decyzyjne
0.998811
0.996441
0.997581
0.997625
2
Las Losowy
1.000000
1.000000
1.000000
1.000000
3
KNN Classifier
1.000000
1.000000
1.000000
1.000000
4
SGD Classifier
0.954439
0.969158
0.991740
0.961742
W oparciu o powyższą tabelę widzimy, że wnioski o modelach poczynione na temat accuracy przekładają się na metryki dot. analizy błędów klasyfikacji. Najlepszymi modeli, nie popełniającymi żadnych błędów, są te modele, które miały najwyższą dokładność tj.: model las losowego oraz model k-najbliższych sąsiadów. Drugim modelem jest model drzewa decyzyjnego. Nie jest on już tak idealny, natomiast nadal świetnie się sprawdza. Przyjrzyjmy się jego macierzy pomyłek:
array([[781, 1],
[ 3, 840]], dtype=int64)
Tak jak wspomniano na początku rozdziału, w kontekście naszego problemu kluczowym czynnikiem jest liczba fałszywych (trujących grzybów) obserwacji zaklasyfikowanych jako pozytywne (grzyby jadalne). W przypadku modelu drzewa decyzyjnego mamy
1
błędów FP, co stanowi około 0,05% obserwacji zbioru testowego. Liczba FP jest o jedną obserwację mniejsza od liczby FN.
Analizę błędów klasyfikatorów liniowych zaczynamy od regresji logistycznej.
Wskaźniki tego modelu wypadają najgorzej. Postać macierzy pomyłek:
array([[742, 40],
[ 28, 815]], dtype=int64)
Macierz pomyłek tego modelu wygląda alarmująco - mamy 40 obserwacji typy FP, co stanowi już:
'2.5%'
wszystkich obserwacji testowych. Stanowi to znaczący odsetek, co powduje, że model regresji logistycznej nie jest odpowiedni do zadanego problemu. Łącznie model tej popełnił
68
błędnych klasyfikacji, z czego aż:
40
błędnych klasyfikacji obserwacji negatywnych jako pozytywne.
Zobaczymy jak przedstawiają się statystyki ostatniego modelu, również liniowego - SGD Classifier. Jeżeli chodzi o metryki z tabeli wyżej, performance modelu wygląda podobnie do modelu regresji logistycznej. Macierz pomyłek:
array([[743, 39],
[ 26, 817]], dtype=int64)
Jest nieco lepiej. Model SGD Classifier popełnił mniej błędów FP, choć w porównaniu do reszty modli liczba ta nadal jest spora. Łącznie algorytm popełnił:
65
błędnych klasyfikacji, z czego
39
klasyfikacji grzybów trujących jako jadalne, co stanowi
'2.4%'
obserwacji testowych.
Podsumowując. Modeli, które sprawdzają się idealnie do tego problemu, są klasyfikatory nieliniowe: las losowy oraz k-najbliższych sąsiadów. Nieco gorzej wypada model pojedynczego drzewa decyzyjnego. Klasyfikatory liniowe sprawdzają się gorzej przy danych, gdzie każda zmienna została zakodowana z użyciem funckji OneHotEncoder. Z małą poprawką, że model z użyciem optymalizacji SGD można prawdopodnie jeszcze nieco ulepszyć, poprzez dalsze poszukiwanie optymalnych parametrów.